/*
 * TransiCast City Visions
 * (c) 2014 Joachim Pfeiffer
 * www.transicast.com
 */
package com.transicast.cityvisions;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.util.HashMap;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ConcurrentHashMap;

import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserFactory;

import android.content.Intent;
import android.media.AudioManager;
import android.media.SoundPool;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.view.View;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;

import com.transicast.cityvisions.R;
import com.metaio.sdk.ARViewActivity;
import com.metaio.sdk.MetaioDebug;
import com.metaio.sdk.jni.IGeometry;
import com.metaio.sdk.jni.IMetaioSDKCallback;
import com.metaio.sdk.jni.Rotation;
import com.metaio.sdk.jni.Vector2d;
import com.metaio.sdk.jni.Vector3d;
import com.metaio.tools.io.AssetsManager;

public class ARView extends ARViewActivity {
	protected static final float						BASE_SCALE = 18.0f;
	private ConcurrentHashMap<String, Arrival>			arrivals = new ConcurrentHashMap<String, Arrival>();
	protected BusesPool									mBusesPool;
	private long										mLastUpdate = 0L;
	private SoundPool									mSoundPool = new SoundPool(1, AudioManager.STREAM_MUSIC, 0);
	private int											mHornSound;
	private int											mAmbientSound;
	private int											mAmbientSoundHandle;
	private long										mLastHornPlayed = 0L;
	private boolean										mIsPlayingAmbientSound;
	private boolean										mHasStartedPlaying;

	private class QueryTransiCast extends AsyncTask<URL, Integer, Long> {

		@Override
		protected Long doInBackground(URL... params) {
			HashMap<String, String> blocks = new HashMap<String, String>();
			try {
				URL url = params[0];
				URLConnection connection = url.openConnection();
				HttpURLConnection httpConnection = (HttpURLConnection)connection;
				httpConnection.setConnectTimeout(2000);
				httpConnection.setReadTimeout(5000);
				int responseCode = httpConnection.getResponseCode();
				if (responseCode == HttpURLConnection.HTTP_OK) {
					InputStream in = httpConnection.getInputStream();
					XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
					XmlPullParser xpp = factory.newPullParser();
					xpp.setInput(new BufferedReader(new InputStreamReader(in, "UTF-8")));
					int eventType = xpp.getEventType();
					String est = "";
					String route = "";
					String dest = "";
					String block = "";
					String locid = "";
					String lat = "";
					String lon = "";
					String heading = "";
					mLastUpdate = System.currentTimeMillis();
					while (eventType != XmlPullParser.END_DOCUMENT) {
						if (eventType == XmlPullParser.START_TAG) {
							String tag = xpp.getName();
							if (tag.equals("arrival")) {
								block = xpp.getAttributeValue(null, "block");
								blocks.put(block,  block);
								route = xpp.getAttributeValue(null, "route");
								est = xpp.getAttributeValue(null, "estimated");
								dest = xpp.getAttributeValue(null, "dest");
								locid = xpp.getAttributeValue(null, "locid");
							}
							if (tag.equals("blockPosition")) {
								lat = xpp.getAttributeValue(null, "lat");
								lon = xpp.getAttributeValue(null, "lng");
								heading = xpp.getAttributeValue(null, "heading");
								if (block.length() > 0) {
									Arrival arrival = arrivals.get(block);
									if (arrival != null) {
										arrival.setRoute(route);
										arrival.setEst(est);
										arrival.setDest(dest);
										arrival.setLocid(locid);
										arrival.setCoordinates(lat, lon);
										arrival.setHeading(heading);
									} else {
										arrival = new Arrival(ARView.this, block, route, est, dest, locid, lat, lon, heading);
										arrivals.put(block, arrival);
									}
								}
								est = "";
								route = "";
								dest = "";
								block = "";
								locid = "";
								lat = "";
								lon = "";
								heading = "";
							}
						}
						eventType = xpp.next();
					}
					for (String b : arrivals.keySet()) {
						if (!blocks.containsKey(b)) {
							Arrival arrival = arrivals.get(b);
							arrival.clear();
							arrivals.remove(b);
						}
					}
					blocks.clear();
					if (mBusesPool != null)
						mBusesPool.cleanup(arrivals);
				}
			} catch (Exception e) {
				final Exception exception = e;
				ARView.this.runOnUiThread(new Runnable() {
					public void run() {
						Toast.makeText(ARView.this, "Error: Internet access problem. " + exception.getCause(), Toast.LENGTH_LONG).show(); 
					} 
				});
				return null;
			}

			return null;
		}
	}

	private class ResumeSound extends TimerTask {
		private int							mSoundId;
		private float 						mLeftVolume;
		private float 						mRightVolume;
		private int							mPriority;
		private int							mLoop;
		private float						mRate;

		@Override
		public void run() {
			mAmbientSoundHandle = mSoundPool.play(mSoundId, mLeftVolume, mRightVolume, mPriority, mLoop, mRate);
		}
		ResumeSound(int soundId, float leftVolume, float rightVolume, int priority, int loop, float rate) {
			mSoundId = soundId;
			mLeftVolume = leftVolume;
			mRightVolume = rightVolume;
			mPriority = priority;
			mLoop = loop;
			mRate = rate;
		}
	}

	Handler timerHandler = new Handler();
	Runnable timerRunnable = new Runnable() {
		@Override
		public void run() {	
			try {
				new QueryTransiCast().execute(new URL("http://transicast.appspot.com/ws/V2/arrivals?locIDs=7264,5638,5657,5671&routes=M_OWL,K_OWL,L_OWL,31,21,9L,N_OWL,71,5L,F,6,9,T_OWL,38L,38,71L,2&agency=SFMTA&appid=sample522are2"));
			} catch (Exception e) {
				final Exception exception = e;
				ARView.this.runOnUiThread(new Runnable() {
					public void run() { Toast.makeText(ARView.this, "Error: " + exception.getMessage(), Toast.LENGTH_LONG).show(); } 
				});
				System.exit(1);
			}

			// request update every 30s, top and bottom of the minute. 
			long now = System.currentTimeMillis();
			long nextUp = now + 30000;
			nextUp = nextUp - nextUp % 30000;
			long wait = nextUp - now; 
			timerHandler.postDelayed(this, wait);
		}
	};

	@Override
	public void onDrawFrame() {
		int directionOneCount = 0;
		int directionTwoCount = 0;
		long now = System.currentTimeMillis();
		for (Arrival arrival : arrivals.values()) {
			String block = arrival.getBlock();
			IGeometry bus = mBusesPool.getBus(block);
			if (bus == null)
				continue;
			double realWordDistanceToStop = arrival.calculateDistanceToStopInterpolated();
			String locId = arrival.getLocid();
			int direction = Parcour.getDirection(locId);

			// Resolve buses overlapping
			float translationX = (float)(realWordDistanceToStop * 3.0d);
			float translationY = 0.0f;
			float translationZ = 0.0f;
			float rotationX = 0.0f;
			float rotationY = 0.0f;
			float rotationZ = 0.0f;
			float scale = BASE_SCALE;
			if (direction > 0) {
				translationZ = -40.0f; // opposite side of the street offset
				rotationY = (float)(Math.PI); // turn by 180 degrees
				translationX -= 2000.0f;
			}
			if (direction < 0)
				translationX = 2000.0f - translationX;

			// Ride into the sunset
			boolean visible = translationX > -760.0f && translationX < 310.0f; // Set invisible if too far away
			bus.setVisible(visible);

			// Keep score of number of visible vehicles per direction 
			if (visible) {
				if (direction > 0)
					directionOneCount++;
				if (direction < 0)
					directionTwoCount++;
			}

			if (translationX < -520.0f) {
				// Left side
				translationY += (translationX - -520.0f) * 0.14f;
				translationZ += (translationX - -520.0f) * 3.6f;
				rotationY += (float)(Math.PI * 1.56);
				if (direction < 0)
					translationX -= 9.0f;
				else
					translationX += 9.0f;
				translationX += (translationX - -520.0f) * 0.2;
			} else
				if (translationX > 250.0f) {
					// Right side
					translationY -= (translationX - 250.0f) * 0.2f;
					translationZ -= (translationX - 250.0f) * 7.2f;
					rotationY += (float)(Math.PI * 0.48);
					if (direction < 0)
						translationX += 10.0f;
					else
						translationX -= 10.0f;
					translationX -= (translationX - 250.0f) * 0.35f;
				}

			bus.setRotation(new Rotation(rotationX, rotationY, rotationZ), false);
			bus.setTranslation(new Vector3d(translationX, translationY, translationZ));
			bus.setScale(scale);
			final Arrival myArrival = arrival;
			final Vector3d location = bus.getTranslation();
			final boolean arrivalVisible = bus.isVisible() && bus.getIsRendered();
			ARView.this.runOnUiThread(new Runnable() {
				public void run() { 
					myArrival.setVisible(arrivalVisible);
					if (arrivalVisible) {
						int coordinateSystemID = metaioSDK.getCoordinateSystemID("MarkerlessCOS");
						Vector2d screenCoords = metaioSDK.getScreenCoordinatesFrom3DPosition(coordinateSystemID, location);
						myArrival.updateBus(myArrival.smooth(screenCoords));
					}
				}
			});
		}

		// No ambient sound if no buses displayed
		if (directionOneCount + directionTwoCount > 0) {
			if (!mIsPlayingAmbientSound) {
				mSoundPool.resume(mAmbientSoundHandle);
				mIsPlayingAmbientSound = true;
				if (!mHasStartedPlaying) {
					mAmbientSoundHandle = mSoundPool.play(mAmbientSound, 1.0f, 1.0f, 0, -1, 1.0f);
					mHasStartedPlaying = true;
				}
			}
		} else {
			if (mIsPlayingAmbientSound) {
				mSoundPool.pause(mAmbientSoundHandle);
				mIsPlayingAmbientSound = false;
			}
		}

		// If busy: Play horn and bus sound
		if (directionOneCount >= 3 || directionTwoCount >= 3)
			if (mLastHornPlayed > 0L) {
				if (now - mLastHornPlayed > 15000L) {
					mSoundPool.play(mHornSound, 1.0f, 1.0f, 0, 0, 1.0f);
					Timer timer = new Timer();
					timer.schedule(new ResumeSound(mAmbientSound, 1.0f, 1.0f, 0, -1, 1.0f), 3000L);
					mLastHornPlayed = now;
				}
			} else
				mLastHornPlayed = now;

		super.onDrawFrame();
	}

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);

		mHornSound = mSoundPool.load(this, R.raw.bushorn, 1);
		mAmbientSound = mSoundPool.load(this, R.raw.busesambient, 1);
	}

	@Override
	public void onPause() {
		super.onPause();
		timerHandler.removeCallbacks(timerRunnable);
		for (Arrival arrival : arrivals.values())
			arrival.setVisible(false);
		mSoundPool.autoPause();
	}

	@Override
	public void onResume() {
		super.onResume();

		// Clear out previous arrivals if last update older than two minutes
		if (mLastUpdate > 0) {
			if (System.currentTimeMillis() - mLastUpdate > 2 * 60 * 1000) {
				arrivals.clear();
				RelativeLayout layout = (RelativeLayout)findViewById(R.id.aroverlay);
				int views = layout.getChildCount();
				for (int i = 0; i < views; i++) {
					View child = layout.getChildAt(i);
					if (child instanceof TextView) {
						layout.removeView(child);
					}
				}
			}
		}

		timerHandler.postDelayed(timerRunnable, 0);

		mSoundPool.autoResume();
	}

	@Override
	protected int getGUILayout() {
		// Attaching layout to the activity
		return R.layout.arview; 
	}

	public void logoClick(View v) {
		startActivity(new Intent("android.intent.action.VIEW", Uri.parse("http://www.transicast.com")));
	}

	@Override
	protected void loadContents() {
		try {
			// Getting a file path for tracking configuration XML file
			String trackingConfigFile = AssetsManager.getAssetPath(getApplicationContext(), "TrackingData_MarkerlessFast.xml");

			// Assigning tracking configuration
			boolean result = metaioSDK.setTrackingConfiguration(trackingConfigFile); 
			MetaioDebug.log("Tracking data loaded: " + result);
			final String[] busModels = new String[5]; 
			for (int i = 0; i < busModels.length; i++)
				busModels[i] = AssetsManager.getAssetPath(getApplicationContext(), "BusModel" + i + ".zip");			
			mBusesPool = new BusesPool(metaioSDK, busModels);

			mIsPlayingAmbientSound = false;
			mHasStartedPlaying = false;
		} catch (Exception e) { }
	}

	@Override
	protected void onGeometryTouched(IGeometry geometry) { }

	@Override
	protected IMetaioSDKCallback getMetaioSDKCallbackHandler() {
		return null;
	}
}
